﻿Imports System.Reflection
Imports System.ComponentModel.DataAnnotations

Public Class ColumnDefinitionAttributeFactory

    Public Sub Create(t As Type, callback As Action(Of ColumnDefinitionAttribute))
        Dim idx As Integer = 0

        '#71「テーブル作成クエリの列の定義順序改善」
        For Each item In (From el In TableAttributeReader.Read(t).Columns Order By If(el.IsPrimaryKey, 0, 1), Array.IndexOf(t.GetProperties, el.PropertyInfo))
            callback.Invoke(Me.Create(item, idx))
            idx += 1
        Next
    End Sub

    Private Function Create(col As ColumnAttribute, idx As Integer) As ColumnDefinitionAttribute
        Dim atr As ColumnDefinitionAttribute = AttributeReader.Read(Of ColumnDefinitionAttribute)(col.PropertyInfo).FirstOrDefault
        Dim mst As ColumnDefinitionAttribute = Me.CreateMaster(col, idx)

        '省略時はマスタを返す
        If atr Is Nothing Then Return mst

        '省略されていない場合はマージする
        If atr.Index = 0 Then atr.Index = mst.Index
        atr.Column = col
        If atr.IsStaticLength Is Nothing Then atr.IsStaticLength = mst.IsStaticLength
        If atr.Size = 0 Then atr.Size = mst.Size
        If String.IsNullOrEmpty(atr.ColumnType) Then atr.ColumnType = mst.ColumnType

        'Display属性を補てんする
        atr.ParentDisplayAttribute = mst.ParentDisplayAttribute
        atr.DisplayAttribute = mst.DisplayAttribute

        'コマンド文字列を再計算する
        If String.IsNullOrEmpty(atr.CommandText) Then atr.CommandText = Me.GetCommandText(atr)

        Return atr
    End Function

    ''' <summary>
    ''' 列規約
    ''' </summary>
    ''' <param name="col"></param>
    ''' <param name="idx"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CreateMaster(col As ColumnAttribute, idx As Integer) As ColumnDefinitionAttribute
        Dim def As New ColumnDefinitionAttribute

        def.Index = idx
        def.Column = col
        def.IsStaticLength = False
        def.Size = 100
        def.IsNullable = col.IsNullable
        def.ColumnType = Me.GetColumnType(def)
        def.CommandText = Me.GetCommandText(def)

        'Display属性を補てんする
        If def.Column.IsParent = False Then
            def.DisplayAttribute = AttributeReader.Read(Of DisplayAttribute)(col.PropertyInfo).FirstOrDefault
            def.ParentDisplayAttribute = Nothing
        Else
            def.DisplayAttribute = AttributeReader.Read(Of DisplayAttribute)(def.Column.PropertyInfo).FirstOrDefault
            def.ParentDisplayAttribute = AttributeReader.Read(Of DisplayAttribute)(DirectCast(def.Column, ParentRelationColumnAttribute).YourColumn.PropertyInfo).FirstOrDefault
        End If

        Return def
    End Function

    Public Overridable Function GetCommandText(def As ColumnDefinitionAttribute) As String
        Dim null As String = If(def.IsNullable, "NULL", "NOT NULL")
        Dim t As String = String.Format(def.ColumnType, def.Size)

        If def.Column.IsPrimaryKey AndAlso TypeOf def.Column.Key Is AutoKeyAttribute AndAlso TableAttributeReader.Read(def.Column.Table.ModelType).IsRootBase Then
            Return String.Format("{0} IDENTITY (1, 1) {1}", t, null)
        Else
            Return String.Format("{0} {1}", t, null)
        End If
    End Function

    Public Function GetColumnType(def As ColumnDefinitionAttribute) As String
        '参考サイト
        'MSDN - CLR パラメータ データのマッピング
        'http://msdn.microsoft.com/ja-jp/library/ms131092(v=sql.100).aspx
        'MSDN - データ型の概要 (Visual Basic)
        'http://msdn.microsoft.com/ja-jp/library/47zceaw7(v=vs.100).aspx

        Dim t As Type = Nothing
        Dim prop As PropertyInfo = def.Column.GetRelationRootColumn.PropertyInfo

        If AttributeReader.Read(Of StringDateColumnAttribute)(prop).FirstOrDefault IsNot Nothing Then
            t = GetType(String)
        ElseIf AttributeReader.Read(Of IntBooleanColumnAttribute)(prop).FirstOrDefault IsNot Nothing Then
            t = GetType(Int32)
        Else
            '#103 親－子－孫の関係時、子、孫を起点とした読み込みができない
            t = PropertyReader.GetActualPropertyType(prop)
        End If

        Return GetColumnType(def, t)
    End Function

    Public Overridable Function GetColumnType(def As ColumnDefinitionAttribute, t As Type) As String
        If t.Equals(GetType(Boolean)) Then
            Return "bit"
        ElseIf t.Equals(GetType(Byte())) Then
            Return "binary({0})"
        ElseIf t.Equals(GetType(Char)) Then
            Return "nchar"
        ElseIf t.Equals(GetType(DateTime)) Then
            Return "datetime2"
        ElseIf t.Equals(GetType(Decimal)) Then
            Return "decimal"
        ElseIf t.Equals(GetType(Double)) Then
            Return "float(53)"
        ElseIf t.Equals(GetType(Int32)) OrElse t.IsEnum Then
            Return "int"
        ElseIf t.Equals(GetType(Int64)) Then
            Return "bigint"
        ElseIf t.Equals(GetType(Int16)) Then
            Return "smallint"
        ElseIf t.Equals(GetType(Single)) Then
            Return "real"
        ElseIf t.Equals(GetType(String)) Then
            If def.IsStaticLength Then
                Return "nchar({0})"
            Else
                Return "nvarchar({0})"
            End If
        Else
            Return "sql_variant"
        End If
    End Function

End Class
